package com.sh.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.common.cache.DictCache;
import com.sh.common.enums.AlgoOutputTypeEnum;
import com.sh.common.enums.BizCodeEnum;
import com.sh.common.enums.PublishedEnum;
import com.sh.common.pojo.bo.AlgoInputParam;
import com.sh.common.pojo.bo.AlgoOutputParam;
import com.sh.common.pojo.dto.AlgoDto;
import com.sh.common.pojo.dto.AlgoParseParamResultDto;
import com.sh.common.pojo.query.AlgoPageQuery;
import com.sh.common.pojo.query.CalcAlgoParseParam;
import com.sh.config.CalcConfig;
import com.sh.entity.Algo;
import com.sh.entity.AlgoOperator;
import com.sh.entity.Operator;
import com.sh.exceptions.BizException;
import com.sh.mapper.AlgoMapper;
import com.sh.mapper.OperatorMapper;
import com.sh.service.IAlgoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 算法 服务实现类
 * </p>
 *
 * @author zhenlan
 * @since 2024-07-11
 */
@Slf4j
@Service
public class AlgoServiceImpl extends ServiceImpl<AlgoMapper, Algo> implements IAlgoService {

    @Autowired
    private CalcConfig calcConfig;

    @Autowired
    private AlgoMapper algoMapper;

    @Autowired
    private OperatorMapper operatorMapper;

    @Override
    public IPage<AlgoDto> queryAlgoPage(AlgoPageQuery query) {
        Page<Algo> page = new Page<>(query.getPageNum(), query.getPageSize());
        page.addOrder(OrderItem.desc("id"));
        QueryWrapper<Algo> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(query.getName())) {
            queryWrapper.like("name", query.getName());
        }
        if (StringUtils.isNotBlank(query.getNameEn())) {
            queryWrapper.like("name_en", query.getNameEn());
        }
        if (StringUtils.isNotBlank(query.getApplicableTypeCode())) {
            queryWrapper.eq("applicable_type_code", query.getApplicableTypeCode());
        }
        if (StringUtils.isNotBlank(query.getSubjectCode())) {
            queryWrapper.eq("subject_code", query.getSubjectCode());
        }
        if (query.getPublished() != null) {
            queryWrapper.eq("published", query.getPublished());
        }
        if (query.getPublishStartTime() != null && query.getPublishEndTime() != null) {
            queryWrapper.between("publish_time", query.getPublishStartTime(), query.getPublishEndTime());
        }
        return algoMapper.selectPage(page, queryWrapper).convert(this::transferToDto);
    }

    @Override
    public AlgoDto transferToDto(Algo algo) {
        if (algo == null) {
            return null;
        }
        AlgoDto algoDto = new AlgoDto();
        BeanUtil.copyProperties(algo, algoDto);
        algoDto.setSubjectName(DictCache.OPERATOR_SUBJECT_MAP.get(algo.getSubjectCode()));
        algoDto.setApplicableTypeName(DictCache.OPERATOR_APPLICABLE_TYPE_MAP.get(algo.getApplicableTypeCode()));
        algoDto.setPublishedName(PublishedEnum.getMessageByCode(algo.getPublished()));
        return algoDto;
    }

    @Override
    public List<AlgoOperator> parseAlgoOperators(String algoSchema) {
        JSONObject jsonObject = JSON.parseObject(algoSchema);
        JSONObject processesObject = jsonObject.getJSONObject("processes");

        // 解析 processes 字段
        List<AlgoOperator> algoOperators = new ArrayList<>();
        for (Map.Entry<String, Object> entry : processesObject.entrySet()) {
            String operatorNodeId = entry.getKey();
            JSONObject processObject = (JSONObject) entry.getValue();
            Long operatorId = processObject.getLong("component");
            JSONObject metadataObject = processObject.getJSONObject("metadata");
            String operatorName = metadataObject.getString("label");

            AlgoOperator algoOperator = new AlgoOperator();
            algoOperator.setOperatorId(operatorId);
            algoOperator.setOperatorNodeId(operatorNodeId);
            algoOperators.add(algoOperator);
        }
        return algoOperators;
    }

    @Override
    public AlgoParseParamResultDto parseAlgoParams(String algoSchema) throws BizException {
        CalcAlgoParseParam calcAlgoParseParam = new CalcAlgoParseParam();
        calcAlgoParseParam.setData(JSON.parse(algoSchema));
        calcAlgoParseParam.setCheck_mapping(true);
        String rep = HttpUtil.createPost(calcConfig.getAlgoParseParamUrl())
                .body(JSON.toJSONString(calcAlgoParseParam))
                .contentType("application/json")
                .execute()
                .body();
        log.info("parse algo result, {}", rep);
        JSONObject jsonObject = JSON.parseObject(rep);
        if (!jsonObject.containsKey("success") || !jsonObject.getBoolean("success")) {
            log.warn("parse algo error!!!");
            throw new BizException(BizCodeEnum.ALGO_PARSE_ERROR.getCode(), jsonObject.getString("message"));
        }
        Map<String, String> operatorIdNameMap = new HashMap<>();

        String data = jsonObject.getString("data");
        JSONObject dataObject = JSON.parseObject(data);
        // 解析算法输入
        JSONObject params = dataObject.getJSONObject("params");
        List<AlgoInputParam> algoInputParams = new ArrayList<>();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String operatorId = entry.getKey();
            if (!operatorIdNameMap.containsKey(operatorId)) {
                Operator operator = operatorMapper.selectById(new Long(operatorId));
                if (operator == null) {
                    log.warn("operator not found, operatorId: {}", operatorId);
                } else {
                    operatorIdNameMap.put(operatorId, operator.getName());
                }
            }
            JSONArray paramArr = (JSONArray) entry.getValue();
            for (int i = 0; i < paramArr.size(); i++) {
                JSONObject paramObject = (JSONObject) paramArr.get(i);
                AlgoInputParam algoInputParam = new AlgoInputParam();
                algoInputParam.setOperatorName(operatorIdNameMap.get(operatorId));
                algoInputParam.setName(paramObject.getString("name"));
                algoInputParam.setValueType(paramObject.getString("type"));
                algoInputParam.setRequired("required".equals(paramObject.getString("optional_field"))?1:0);
                algoInputParam.setDefaultValue(paramObject.getString("default_val"));
                algoInputParams.add(algoInputParam);
            }
        }
        // 解析算法输出
        JSONObject results = dataObject.getJSONObject("results");
        List<AlgoOutputParam> algoOutputParams = new ArrayList<>();
        for (Map.Entry<String, Object> entry : results.entrySet()) {
            JSONArray resultArr = (JSONArray) entry.getValue();
            for (int i = 0; i < resultArr.size(); i++) {
                JSONObject resultObject = (JSONObject) resultArr.get(i);
                AlgoOutputParam algoOutputParam = new AlgoOutputParam();
                algoOutputParam.setOperatorName(operatorIdNameMap.get(entry.getKey()));
                algoOutputParam.setName(resultObject.getString("name"));
                algoOutputParam.setValueType(resultObject.getString("type"));
                algoOutputParam.setOutputTypeName(AlgoOutputTypeEnum.RESULT.getMessage());  // 先写死
                algoOutputParams.add(algoOutputParam);
            }
        }
        return AlgoParseParamResultDto.builder()
                .algoInputParams(algoInputParams)
                .algoOutputParams(algoOutputParams)
                .build();
    }
}
